﻿using System;
using System.Collections.Generic;
using System.Linq;
using BFlatCompile.Nodes;

namespace BFlatCompile.Serializer
{
	internal static class Phase05FlattenClassesAndResolveSubclasses
	{
		public static Class[] Do(Namespace[] rootNamespaces, Dictionary<string, Namespace> rootNamespaceLookup, Dictionary<object, object> parentLookup)
		{
			List<Class> allClassesList = new List<Class>();
			foreach (Namespace ns in rootNamespaces)
			{
				allClassesList.AddRange(SimplifyClass(ns, parentLookup, rootNamespaceLookup));
			}
			return allClassesList.ToArray();
		}

		private static Class[] SimplifyClass(Namespace ns, Dictionary<object, object> parents, Dictionary<string, Namespace> rootNamespaces)
		{
			List<Class> output = new List<Class>();
			foreach (Namespace sns in ns.Namespaces)
			{
				output.AddRange(SimplifyClass(sns, parents, rootNamespaces));
			}

			foreach (Class c in ns.Classes)
			{
				output.AddRange(SimplifyClass(c, parents, rootNamespaces));
			}

			return output.ToArray();
		}

		private static Class[] SimplifyClass(Class c, Dictionary<object, object> parents, Dictionary<string, Namespace> rootNamespaces)
		{
			List<Class> allClasses = new List<Class>() { c };

			if (c.BaseClass != null)
			{
				object baseClassObject = ResolveReference(c.BaseClass, c, parents, rootNamespaces);
				if (baseClassObject == null)
				{
					throw new Exception("Class not found."); // TODO: token info
				}

				if (!(baseClassObject is Class))
				{
					throw new Exception("Token is not a class name"); // TODO: show the token
				}

				c.BaseClassInstance = (Class)baseClassObject;
				if (c.BaseClassInstance.IsStatic)
				{
					throw new ParserException("Cannot extend from a static class.", null);
				}
			}

			foreach (Class nestedClass in c.NestedClasses)
			{
				Class[] moreClasses = SimplifyClass(nestedClass, parents, rootNamespaces);
				allClasses.AddRange(moreClasses);
			}

			return allClasses.ToArray();
		}

		// TODO: this is where using directives will be implemented. Usings will only work on the first
		// step of a traversal, which is where this function is called. 
		// If there is a using hit, call ResolveReferenceTraverseDown instead of up.
		private static object ResolveReference(string name, object scope, Dictionary<object, object> parents, Dictionary<string, Namespace> rootNamespaces)
		{
			if (name == null) return null;
			List<string> steps = new List<string>(name.Split('.'));
			string first = steps[0];
			steps.RemoveAt(0);
			return ResolveReferenceTraverseUp(first, steps, scope, parents, rootNamespaces);
		}

		private static object ResolveReferenceTraverseDown(object scope, List<string> steps)
		{
			if (steps.Count == 0)
			{
				return scope;
			}

			string first = steps[0];
			steps.RemoveAt(0);

			object hit = GetLocalScopeIfExists(scope, first);

			if (hit == null)
			{
				return null;
			}

			return ResolveReferenceTraverseDown(hit, steps);
		}

		private static object GetLocalScopeIfExists(object scope, string name)
		{
			if (scope is Method || scope is Property || scope is Field)
			{
				return null;
			}

			if (scope is Class)
			{
				Class classScope = (Class)scope;

				foreach (Field f in classScope.Fields)
				{
					if (f.IsStatic && f.Name == name)
					{
						return f;
					}
				}

				foreach (Method m in classScope.Methods)
				{
					if (m.IsStatic && m.Name == name)
					{
						return m;
					}
				}

				foreach (Property p in classScope.Properties)
				{
					if (p.IsStatic && p.Name == name)
					{
						return p;
					}
				}

				foreach (Class c in classScope.NestedClasses)
				{
					if (c.Name == name)
					{
						return c;
					}
				}

				return null;
			}

			if (scope is Namespace)
			{
				Namespace nsScope = (Namespace)scope;

				foreach (Class c in nsScope.Classes)
				{
					if (c.Name == name)
					{
						return c;
					}
				}

				foreach (Namespace ns in nsScope.Namespaces)
				{
					if (ns.Name == name)
					{
						return ns;
					}
				}

				return null;
			}

			return null;
		}

		private static object ResolveReferenceTraverseUp(string first, List<string> steps, object scope, Dictionary<object, object> parents, Dictionary<string, Namespace> rootNamespaces)
		{
			// this tree is built from the parse tree, not references, so not infinite loop
			while (scope != null)
			{
				object hit = GetLocalScopeIfExists(scope, first);
				if (hit != null)
				{
					return ResolveReferenceTraverseDown(hit, steps);
				}
				scope = parents[scope];
			}

			if (rootNamespaces.ContainsKey(first))
			{
				return ResolveReferenceTraverseDown(rootNamespaces[first], steps);
			}

			return null;
		}
	}
}
